﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Nage.Examples.Rastrigin.Evolution;
using Nage.Algorithms.Population;
using Nage.Algorithms.Solution;
using Nage.Algorithms.Evolution;
using Nage.Algorithms.RandomGenerators;
using Utils.Logger;
using Nage.Examples.Rastrigin.Solution;
using Nage.Examples.Rastrigin.Population;

namespace Nage.Examples.Rastrigin.Evolution
{
    public class CoordinatesEvolution : Evolution<Coordinates>
    {
        #region Public properties
        public Double MutationRate { get; private set; }
        public Double CrossoverRate { get; private set; } 
        #endregion

        #region Private fields
        private IPopulation<Coordinates> _currentPopulation;
        private IPopulation<Coordinates> _nextPopulation;

        private double _totalFitness;
        private List<double> _fitnessList;
        private IEnumerable<Coordinates> _sortedPopulation;

        private bool firstGeneration;
        #endregion

        #region Constructor
        public CoordinatesEvolution(double mutationRate, double delta, double crossoverRate)
        {
            CLogger.LogTrace("Creating CoordinatesEvolution");

            MutationRate = mutationRate;
            CrossoverRate = crossoverRate;
            Mutation = new CoordinatesMutation(mutationRate, delta);
            Recombination = new CoordinatesRecombination();

            _fitnessList = new List<double>();
            firstGeneration = true;
        } 
        #endregion

        #region Evolve
        public override IPopulation<Coordinates> Evolve(IPopulation<Coordinates> population)
        {
            CLogger.LogDebug("CoordinatesEvolution -> Evolve");
            _currentPopulation = population;

            if (firstGeneration)
            {
                RankPopulation();
                firstGeneration = false;
            }

            CreateNextGeneration();
            RankPopulation();
            ShiftGenerations();

            return _currentPopulation;
        } 
        #endregion

        #region RankPopulation
        private void RankPopulation()
        {
            CLogger.LogDebug("Ranking current population.");
            _totalFitness = 0;
            foreach (Coordinates coords in _currentPopulation)
            {
                ISolutionEvaluator<Coordinates> evaluator = _currentPopulation.GetEvaluation(coords);
                double coordFitness = evaluator.Evaluate(coords);
                coords.Fitness = coordFitness;
                _totalFitness += coordFitness;
            }

            _sortedPopulation = _currentPopulation.OrderByDescending(x => x.Fitness);

            _fitnessList.Clear();
            double fitnessAggregate = 0.0;
            foreach (Coordinates coords in _sortedPopulation)
            {
                CLogger.LogDebug("\t{0} - fitness: {1}", coords, coords.Fitness);
                fitnessAggregate += coords.Fitness;
                _fitnessList.Add(fitnessAggregate);
            }
        } 
        #endregion

        #region CreateNextGeneration
        private void CreateNextGeneration()
        {
            CLogger.LogDebug("Creating next generation.");
            _nextPopulation = new CoordinatesPopulation();
            while (_nextPopulation.Count < _currentPopulation.Count)
            {
                Coordinates parentA = RouletteSelection();
                CLogger.LogDebug("Chosen parentA: {0}", parentA);
                Coordinates parentB = RouletteSelection();
                CLogger.LogDebug("Chosen parentB: {0}", parentB);
                Coordinates childA = parentA;
                Coordinates childB = parentB;

                if (Rng.GetInstance().Draw(CrossoverRate))
                {
                    CLogger.LogDebug("Recombining parents.");
                    childA = Recombination.Recombine(parentA, parentB);
                    childB = Recombination.Recombine(parentA, parentB);
                }

                CLogger.LogDebug("Mutating childA.");
                childA = Mutation.Mutate(childA);
                CLogger.LogDebug("Mutating childB.");
                childB = Mutation.Mutate(childB);

                CLogger.LogDebug("Overall changes:\n\twas {0} {1}\n\tis {2} {3}", parentA, parentB, childA, childB);

                _nextPopulation.Add(childA);
                _nextPopulation.Add(childB);
            }
        } 
        #endregion

        #region RouletteSelection
        private Coordinates RouletteSelection()
        {
            CLogger.LogDebug("Roulette selection");
            double randomFitness = Rng.GetInstance().GetDouble() * _totalFitness;
            int idx = -1;
            int mid;
            int first = 0;
            int last = _currentPopulation.Count - 1;
            mid = (last - first) / 2;

            while (idx == -1 && first <= last)
            {
                if (randomFitness > _fitnessList[mid])
                {
                    last = mid;
                }
                else if (randomFitness < _fitnessList[mid])
                {
                    first = mid;
                }
                mid = (first + last) / 2;
                if ((last - first) == 1)
                    idx = last;
            }

            return _sortedPopulation.ElementAt(idx);
        } 
        #endregion

        #region ShiftGenerations
        private void ShiftGenerations()
        {
            _currentPopulation = _nextPopulation;
            _nextPopulation = null;
        } 
        #endregion
    }
}
